/***************************************************
 * 此文件的版权归属xxxx公司所有，未经允许，请勿复制
 */
package com.pxxy.cels.controller.impl;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import com.pxxy.cels.controller.Controller;
import com.pxxy.cels.entity.BaseTerm;
import com.pxxy.cels.entity.NoteBook;
import com.pxxy.cels.entity.PageBean;
import com.pxxy.cels.entity.TestDetail;
import com.pxxy.cels.entity.TestRecord;
import com.pxxy.cels.entity.Vocabulary;
import com.pxxy.cels.entity.Word;
import com.pxxy.cels.exceptions.NoHistoryException;
import com.pxxy.cels.exceptions.NoTestRecordException;
import com.pxxy.cels.service.IBrowserService;
import com.pxxy.cels.service.IGameService;
import com.pxxy.cels.service.INoteBookService;
import com.pxxy.cels.service.ITestingService;
import com.pxxy.cels.service.impl.BrowserServiceImpl;
import com.pxxy.cels.service.impl.GameServiceImpl;
import com.pxxy.cels.service.impl.NoteBookServiceImpl;
import com.pxxy.cels.service.impl.TestingServiceImpl;
import com.pxxy.cels.ui.IView;
import com.pxxy.cels.ui.impl.CommandView;
import com.pxxy.cels.util.DateUtil;
import com.pxxy.cels.util.InputUtil;
import com.pxxy.cels.util.StringUtil;
import com.pxxy.cels.util.VerifyFactory;

/************************************************
 * @author yejf
 * @since JDK7.0
 * @CreateDate 2020年4月9日 上午9:56:24
 * @description 此类用来描述了xxxxx
 *
 */
public class CelsController implements Controller {

	/** 浏览单词时，每页显示多少个单词，默认为 10 */
	private int words_per_page = 10;

	/** 查看测试记录时，每次显示几条，默认为 3 */
	private int counts_per_test = 3;

	/** 表现层对象 */
	private IView view;

	// 业务层属性
	/** 浏览业务对象 */
	private IBrowserService browserService;
	/** 做游戏业务对象 */
	private IGameService gameService;
	/** 测试业务对象 */
	private ITestingService testingService;
	/** 生成库对象 */
	private INoteBookService noteBookService;

	public CelsController() {
		// 初始化这些属性
		this.view = new CommandView();
		//
		this.browserService = new BrowserServiceImpl();
		this.gameService = new GameServiceImpl();
		this.testingService = new TestingServiceImpl();
		this.noteBookService = new NoteBookServiceImpl();
	}

	@Override
	public void action() throws Exception {
		//
		view.welcome();
		// 定义变量
		int choice = -1;
		boolean exit = false;
		boolean rtnTop = false;
		while (!exit) {
			// 显示主菜单
			view.mainMenu();
			// 提示用户选择
			choice = InputUtil.getInt("请选择>");
			// 把返回上一次的控制变量重置为false
			rtnTop = false;
			// 利用switch
			switch (choice) {
			case 1:
				// 执行业务1
				while (!rtnTop) {
					view.showBrowserSubMenu();
					// 继续让用户选择
					choice = InputUtil.getInt("请选择具体功能>");
					// 继续对子菜单进行处理
					switch (choice) {
					case 1:
						// TODO 完成业务1的具体功能1 -- 浏览单词
						Map<Character, Set<Word>> firstMap = browserService.getFirstMap();
						Set<Character> keySet = firstMap.keySet();
						//
						for (Character first : keySet) {
							System.out.printf("%-2s[%-2d]%-4s", first, firstMap.get(first).size(), " ");
							// 当字母为 g、n、t、z 时，换行，最多四行
							char temp = first.charValue();
							if (temp == 'G' || temp == 'N' || temp == 'T' || temp == 'Z') {
								System.out.println(); // 换行
							}
						}
						System.out.println("\n___________________________________");
						// 提示，用户选择首字母
						char letter = InputUtil.getChar("请您选择要查看的首字母>");
						// 如果有小写字母，转换成大写的
						letter = Character.isLowerCase(letter) ? (char) (letter - 32) : letter;
						// 转换成包装类 对象
						Character first = Character.valueOf(letter);
						// 获取要显示的数据
						Set<Word> wordSet = firstMap.get(first);
						// 输出所有的以此首字母打头的单词
						System.out.println(); // 空行
						int count = 1;
						for (Word w : wordSet) {
							System.out.printf("%s [%d. %s => 解释：%s]\n\n", "☆", count, w.getEn(),
									Arrays.toString(w.getCn()));
							// 每页显示 words_per_page 个单词，如果某个字母的单词数刚好是words_per_page的倍数，则最后一页显示完后，就无需再提示 是否查看
							// 下一页 了
							if (count % words_per_page == 0 && count != wordSet.size()) {
								//
								char tip = InputUtil.getChar("是否查看 下一页[y,n]?");
								// 比较
								if (tip == 'n' || tip == 'N') {
									// 用户不想看 下一页
									break;
								}
							}
							count++;
						}
						System.out.println(" .... ... ... ...\n"); // 空行

						break;
					case 2:
						// 浏览词汇
						PageBean<Vocabulary> pb = browserService.getPageVocabularies();
						// 通过PageBean获取相关的信息
						int pages = pb.getPages(); // 获取总页数
						int size = pb.getSize(); // 获取每页的行数
						// 循 环
						while (true) {
							// 显示分页的菜单
							System.out.printf("每页【%d】行 ※ 共计【%d】页 ※ 词汇量总数: 【%d】.   返回上一级  0 \n", size, pages,
									pb.getTotal());
							// 打印分页样式
							System.out.println(pb.getShowStyle());
							System.out.println("__________________________________");
							// 提示用户选择第几页查看
							int page = InputUtil.getInt("请选择页码【%d ~ %d】>", pb.getFirst(), pb.getPages());
							// 输入0，表示返回上一级
							if (page == 0) {
								// 返回到上一级
								break;
							}
							// 再根据页码来获取数据
							List<Vocabulary> showData = pb.getShowData(page);
							// 打印数据
							count = 1;
							// 输出头
							System.out.printf("第【%d】页数据有：\n", pb.getCurrent());
							System.out.println("__________________________________\n");
							for (Vocabulary v : showData) {
								System.out.printf("%s [%d. %s => 解释：%s]", "※", count, v.getEn(),
										Arrays.toString(v.getCn()));
								String acronym = v.getAcronym();
								if (acronym != null) {
									// 打印输出
									System.out.printf("%6s:%s", "简称", v.getAcronym());
								}
								System.out.println("\n");
								count++;
							}
							System.out.println(" .... ... ... ... .... ...\n"); // 空行

						} // 循环结束
						break;
					case 0:
						// 表示回到上一级菜单
						rtnTop = true;
						break;
					default:
						System.out.println("警告：你的子菜单选择不正确");
						break;
					}
				}
				break;

			case 2:
				// 执行业务2
				while (!rtnTop) {
					view.showGameSubMenu();
					// 继续让用户选择
					choice = InputUtil.getInt("请选择具体功能>");
					// 继续对子菜单进行处理
					switch (choice) {
					case 1:
						// 选择了 提示英文，回答中文[只要答对一个就算对，汉字要输入正确
						// 1. 获取业务数据
						Map<String, String> en2cnMap = null;
						try {
							// 提示是否要从上次开始
							char tip = InputUtil.getChar("%4s 是否要从上次继续? Y or N >", "※");
							// 判断
							if (tip == 'Y' || tip == 'y') {
								// 说明用户的数据从历史 记录中加载
								en2cnMap = gameService.en2cn(false);
							} else {
								// 说明用户从新开始
								en2cnMap = gameService.en2cn(true);
							}

						} catch (NoHistoryException e) {
							// 出现异常，也就是没有历史记录，则直接从重新开始
							System.out.println("警告：你没有历史记录，所以，还是从新开始！ ");
							try {
								en2cnMap = gameService.en2cn(true);
							} catch (NoHistoryException e1) {
								e1.printStackTrace();
							}
						}
						// 开始互答
						onGame(en2cnMap, true);
						break;
					case 2:
						// 你选择了 提示中文，回答英文[不能有拼写错误，不区分大小写]
						// 1. 获取业务数据
						Map<String, String> cn2enMap = null;
						try {
							// 提示 是否要从上次继续
							char tip = InputUtil.getChar("%4s 是否要从上次继续? Y or N >", "※");
							// 判断
							if (tip == 'Y' || tip == 'y') {
								// 说明用户的数据从历史 记录中加载
								cn2enMap = gameService.cn2en(false);
							} else {
								// 说明用户从新开始
								cn2enMap = gameService.cn2en(true);
							}
							//
						} catch (NoHistoryException e) {
							// 出现异常，说明是第一次使用，或没有历史记录，则直接从重新开始
							System.out.println("警告：你没有历史记录，所以，还是从新开始！ ");
							try {
								cn2enMap = gameService.cn2en(true);
							} catch (Exception e1) {
								e1.printStackTrace();
							}
						}
						// 开始操作
						onGame(cn2enMap, false);
						break;
						
					case 0:
						// 表示回到上一级菜单
						rtnTop = true;
						break;
					default:
						System.out.println("警告：你的子菜单选择不正确");
						break;
					}

				}
				break;

			case 3:
				// 执行业务3
				while (!rtnTop) {
					view.showTestingSubMenu();
					// 继续让用户选择
					choice = InputUtil.getInt("请选择具体功能>");
					// 继续对子菜单进行处理
					switch (choice) {
					case 1:
						// 你选择了 开始测试
						int capacity = InputUtil.getInt("请输入要测试的题目数量[%d ~ %d]>", 5, 100);
						//获取测试数据 通过业务对象
						List<BaseTerm> testDatas = testingService.getTestingData(capacity);
						//开始测试
						onTesting(testDatas); 
						break;
					case 2:
						//1.获取历史记录
						List<TestRecord> records = null;
						try {
							records = testingService.reviewTestRecord();
							//如果存在，则迭代出来，因为，这个记录我们是已经按测试时间排过序的
							for (int i = 0; i < records.size(); i++) {
								//首先，只显示最近的3次，然后，提示用户是否要查看所有的历史记录
								if(i != counts_per_test) { 
									System.out.println(records.get(i));
								} else {
									//提示用户，是否要继续
									char letter = InputUtil.getChar("[y or n]是否要继续? >");
									if(letter == 'n' || letter == 'N') {
										break;
									}
								}
							}
							
						} catch (NoTestRecordException e) {
							//说明没有过历史记录
							System.out.println(e.getMessage());
						}
						break;
					case 0:
						// 表示回到上一级菜单
						rtnTop = true;
						break;
					default:
						System.out.println("警告：你的子菜单选择不正确");
						break;
					}
				}
				break;
				
			case 4:
                while (!rtnTop) {
                    view.showNoteBookSubMenu();
                    choice = InputUtil.getInt("请选择具体功能>");
                    switch (choice) {
                        case 1:
                            reviewNoteBook();
                            System.out.println("------------------------------");
                            break;
                        case 2:
                            browseNoteBook();
                            System.out.println("-------------------------------");
                            break;
                        case 0:
                            System.out.println("返回上级菜单");
                            rtnTop = true;
                            break;
                        default:
                            break;
                    }
                }
                break;	

			case 0:
				// 退出程序
				System.out.println(":)程序结束，欢迎下次使用!");
				// 把exit变量置为true
				exit = true;
				break;
			default:
				System.out.println("警告：你的菜单选择不正确");
				break;
			}
		}
	}

	/**************************************
	 * 游戏业务
	 * 
	 * @param map  玩回答单词或词汇游戏时的数据
	 * @param flag 如果为true，则表示是由en2cn 业务来的请求，则写入到en2cn的历史文件中，否则， 则表示是由 cn2en
	 *             的业务来的请求，则写入到 cn2en 的历史文件中
	 */
	private void onGame(Map<String, String> map, boolean flag) {
		//使用一个List来存放回答错误的单词，并写入到生词库中
		List<NoteBook> noteBookList = new ArrayList<NoteBook>();
		// 定义变量，保存本次玩的结果
		int count = 0; // 记录本次玩了多少个题目
		double correct = 0; // 记录本次答对的题目个数
		// 定义不同的游戏规则，提示符不一样
		String tip = "";
		if (flag) {
			tip = "中文";
		} else {
			tip = "英文";
		}

		// 1.创建一个新的Map，存放暂时还没有玩过的数据，起始数据与 形参 map一样
		Map<String, String> backupMap = new HashMap<String, String>(map);
		// 2.迭代map，依次询问用户回答
		Set<Entry<String, String>> entrySet = map.entrySet();
		long start = System.currentTimeMillis(); // 用于记录本次游戏 的时长

		for (Entry<String, String> entry : entrySet) {
			String key = entry.getKey(); // 获取 key，也就是要打印出来让用户回答的题目
			String value = entry.getValue();
			// 输出
			String answer = InputUtil.getString("【退出: e or q】 %4s〖%s〗的%s是： ", "※", key, tip);
			// 判断用户是否输入退出指令
			if (answer.equalsIgnoreCase("e") || answer.equalsIgnoreCase("q")) {
				break;
			}

			count++; //

			// 判断 用户是否答对
			if (isRight(answer, value.split(","), flag)) {
				// 回答正确
				correct++;
				System.out.printf("√ 回答正确, 完整解释是：【%s】\n", value);
				System.out.println("————————————————");
				// 只有回答正确的单词或词汇 才从原集合中删除
				backupMap.remove(key);

			} else {
				// 回答错误
				System.out.printf("〤回答错误,完整解释是：【%s】\n", value);
				System.out.println("————————————————");
				//中文回答错误时，添加到生词库
				if(flag) {
					noteBookList.add(new NoteBook(key, value.split(",")));
				}
			}

		}
		long end = System.currentTimeMillis(); // 用于记录本次游戏的时长

		// 循环退出后，说明用户结束了本次游戏
		// 1. 把剩下没有回答的集合中的数据保存起来，方便以后用户再次游戏
		if (backupMap.size() < map.size()) { // 表示，如果所有都回答错误，那就相录于本次游戏没有改变结果，无需再写入到文件
			// 由flag 决定，要把这个历史记录存放到哪个文件中
			gameService.saveGameHistory(backupMap, flag);
		}

		if(count != 0) {
			// 2. 输出消息，告诉用户本次共游戏了多少个词汇，并正确了多少，正确率是多少。
			double zql = correct / count;
			
			System.out.printf("本次游戏共回答了【%d】次，正确个数【%.0f】,正确率 【%.1f%%】, 共花费时长：【%s】.\n", count, correct, zql * 100,
					DateUtil.convertSeconds((end - start) / 1000));
			if (zql < 0.75) {
				System.out.println("计算机英文水平较差哦  :(  :(  :(\n");
			} else {
				System.out.println(":)  :)  :) 哎哟！不错哦\n");
			}
			
			//查看是否有生词
			try {
				if(noteBookList.size() > 0) {
					System.out.println("---英文到中文：开始写入生词库: "+noteBookList.size());
					this.noteBookService.saveNoteBook(noteBookList);
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			
		} else {
			//表示用户一个都没有回答
			System.out.println("\n别玩了，你一个都没有回答，叫我怎么显示结果呢...\n");
		}

	}

	/*************************************
	 * 英文测试
	 * 
	 * @param testDatas
	 */
	private void onTesting(List<BaseTerm> testDatas) {
		//存放回答错误的单词
		List<NoteBook> noteBookList = new ArrayList<NoteBook>();
		
		// 定义变量，保存本次测试的一些结果
		LocalDateTime d = LocalDateTime.now(); // 获取测试时间
		long start = System.currentTimeMillis(); // 测试开始时间
		int correct = 0; // 正确个数
		int error = 0; // 错误个数
		int total = testDatas.size(); // 题目总个数
		int count = 0; // 记录本次测试了多少个题目

		// 创建一个测试对象
		TestRecord record = new TestRecord();
		// 由于要考虑用户中途退出，所以，我们要创建另一集合，来保存用户回答过的数据
		List<TestDetail> tested = new ArrayList<>();
		// 循环，提示用户回答
		int seq = 0;
		for (BaseTerm bt : testDatas) {
			String en = bt.getEn();
			// 输出
			String answer = InputUtil.getString("【退出: e or q】 %4s %d〖%s〗的中文含义是： ", "※", count + 1, en);
			// 判断用户是否输入退出指令
			if (answer.equalsIgnoreCase("e") || answer.equalsIgnoreCase("q")) {
				break;
			}
			// 把回答过的单词添加到 tested集合中
			TestDetail td = new TestDetail();
			td.setCn(String.join(",", bt.getCn()));
			td.setEn(bt.getEn());
			td.setSeq(++seq);
			// 把测试过的单词 添加进 tested集合中
			tested.add(td);
			// 测试个数++
			count++;
			// 判断 用户回答是否正确
			String[] cn = bt.getCn();
			// 判断 用户是否答对
			if (isRight(answer, bt.getCn(), true)) {
				// 回答正确
				correct++;
				System.out.printf("√ 回答正确, 完整解释是：【%s】\n", String.join("|", cn));
				System.out.println("_________________________________________________");
				// 设置
				td.setCorrect(true);
			} else {
				// 回答错误
				error++;
				System.out.printf("〤回答错误,完整解释是：【%s】\n", String.join("|", cn));
				System.out.println("_________________________________________________");
				//
				td.setCorrect(false);
				//添加到生词库中
				noteBookList.add(new NoteBook(en, cn));
			}
		}
		// 测试结束 时间
		long end = System.currentTimeMillis();
		// 只有当用户参与了测试，才需要写到历史文件中
		if (tested.size() > 0) {
			// 更新 TestRecord 对象的属性
			record.setDatas(tested); // 设置数据
			record.setCorrect(correct); // 设置正确数
			record.setCount(count); // 设置回答题目数
			record.setError(error); // 设置错误数
			record.setTotal(total); // 测试总题数
			record.setSeconds((end - start) / 1000);// 设置测试时长
			record.setTestDate(d); // 设置测试时间

			// 循环结束后，要写文件
			testingService.saveTestRecord(record);
			// 然后，打印一些提示信息.
			double zql = (double) correct / count; // 求正确率 , 本处以 count 为分母，而不是 total，也是考虑到用户中途结束测试.
			System.out.printf("本测试共有【%d】个题目，回答了【%d】个，正确【%d】个, 错误【%d】个, 正确率 【%.1f%%】, 共花费时长：【%s】.\n", total, count,
					correct, error, zql * 100, DateUtil.convertSeconds((end - start) / 1000));

			if (zql < 0.7) {
				System.out.println("考得太差了，加油哦  :(  :(  :(\n");
			} else {
				System.out.println(":)  :)  :) 哎哟！考得不错哦\n");
			}
			
			//写入到生词库中
			try {
				System.out.println("测试时产生的生词："+noteBookList.size()+" 写入到生词库中");
				this.noteBookService.saveNoteBook(noteBookList);
			} catch (IOException e) {
				e.printStackTrace();
			}

		} else {
			// 说明用户直接退出了，一道题都没有答，则提示：
			System.out.println("本次考试一题都没有答，将不会保存记录.");
		}
	}

	/************
	 * 判断 answer是否存在于 数组中
	 * 
	 * @param answer   答案
	 * @param valueArr 数组
	 * @param flag     区别中英文对互译的标志
	 * @return
	 */
	private boolean isRight(String answer, String[] valueArr, boolean flag) {
		//
		boolean result = false;
		// 只要回答的值与原始中的解释有任一一个匹配，都算正确
		if (flag) {
			// 提示英文,回答中文, 可以模糊匹配, 比如: 原意是: 很好的, 我们答了: 好的. 也将算为正确
			for (int i = 0; i < valueArr.length; i++) {
				//
				if (valueArr[i].contains(answer)) {
					return true;
				}
			}
		} else {
			// 提示中文,回答英文,需要完全一样
			for (int i = 0; i < valueArr.length; i++) {
				if (answer.equalsIgnoreCase(valueArr[i])) {
					return true;
				}
			}
		}

		return result;
	}

	/****
	 * 浏览生词库
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	private void reviewNoteBook() throws IOException, ClassNotFoundException {
        int size;//读取多少量
        List<NoteBook> list = noteBookService.getNoteBook();
        int listSize = list.size();//总共多少 个生词
        if (listSize == 0) {
            System.out.println("没有生词~");
            return;
        }
        boolean b;
        do {
            System.out.printf("共[%d]个生词;请输入您本轮要复习的词汇量 ->", listSize);
            size = Integer.parseInt((String) VerifyFactory.verify(VerifyFactory.TYPE_INT_NUMBER));
            b = size > 0 && size <= listSize;
            if (!b) {
                System.out.println("输入不合法,请重写输入");
            }
        } while (!(b));
        Iterator<NoteBook> iterator = list.iterator();
        int count = 1;//计算 循环了多少次
        int allcount=1;//总次数
        while (iterator.hasNext()) {
            NoteBook noteBook = iterator.next();
            int correctTimes = noteBook.getCorrectTimes();
            String[] cn = noteBook.getCn();
            //如果总共答对了三次 则移除
            System.out.printf("(按q 退出)\t[%s] 中文是:=>", noteBook.getEn());
            String answer = (String) VerifyFactory.verify(VerifyFactory.TYPE_STRING_CN_Q);
            if (answer.equalsIgnoreCase("q")) {
                break;
            }
            if (StringUtil.compareString(answer, cn)) {
                correctTimes++;  //答对一次 则记录
                System.out.printf("回答正确，解释是:%s\n", Arrays.toString(cn));
            } else {
                System.out.printf("回答错误，解释是:%s\n", Arrays.toString(cn));
            }
            if (correctTimes >= 3) {
                iterator.remove();
            }
            noteBook.setCorrectTimes(correctTimes);
            if (allcount++==listSize){
                break;
            }
            if (count++ >= size) {
                System.out.print("本轮已复习完 是否继续?(y or n)->");
                String isContinue = (String) VerifyFactory.verify(VerifyFactory.TYPE_STRING_YN);
                if (isContinue.equalsIgnoreCase("n")) {
                    break;
                }
                count = 1;
            }


        }
        System.out.println("练习结束");
        noteBookService.saveNoteBookByReview(list);

    }
	
	private void browseNoteBook() throws IOException, ClassNotFoundException {
        List<NoteBook> list = noteBookService.getNoteBook();
        int size = list.size();
        if (list.size() == 0) {
            System.out.println("没有生词~");
            return;
        }
        int start = 0;
        int end = 6;
        String isQuit;
        boolean b = true;
        do {
            if (end >= size) {
                end = size;
                b = false;
            }
            List<NoteBook> subList = list.subList(start, end);

            for (int j = 0; j < subList.size(); j++) {
                System.out.printf("%s\t\n", subList.get(j).toString());
            }
            start = end;
            end += 10;
            if (b) {
                System.out.print("是否查看下一页[Y,N] ->");
                isQuit = (String) VerifyFactory.verify(VerifyFactory.TYPE_STRING_YN);//数据验证
                if (isQuit.equalsIgnoreCase("n")) {
                    break;
                }
            }
        } while (b);
    }
	
	public static void main(String[] args) throws Exception {
		Controller c = new CelsController();
		c.action();
	}
}
